﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;

namespace SharpDepend.Storage
{
    public class CustomNode
    {
        private List<Property> mProperties;
        private List<CustomNode> mNodes;

        /// <summary>
        /// Get or set the name of this node.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Get collection of properties.
        /// </summary>
        public ReadOnlyCollection<Property> Properties { get; private set; }

        /// <summary>
        /// Get collection of nodes.
        /// </summary>
        public ReadOnlyCollection<CustomNode> Nodes { get; private set; }

        /// <summary>
        /// Get readable text from this node.
        /// </summary>
        public string ReadableText
        {
            get
            {
                return ToString(true);
            }
        }

        /// <summary>
        /// Create instance of CustomNode.
        /// </summary>
        public CustomNode()
        {
            mProperties = new List<Property>();
            mNodes = new List<CustomNode>();
            Properties = new ReadOnlyCollection<Property>(mProperties);
            Nodes = new ReadOnlyCollection<CustomNode>(mNodes);

            Name = string.Empty;
        }

        /// <summary>
        /// Create instance of CustomNode.
        /// </summary>
        /// <param name="parent">Parent to include this node into.</param>
        public CustomNode(CustomNode parent)
            : this()
        {
            parent.mNodes.Add(this);
        }

        /// <summary>
        /// Add child node to this node..
        /// </summary>
        /// <param name="CustomNode">Child node.</param>
        public void AddNode(CustomNode node)
        {
            mNodes.Add(node);
        }

        /// <summary>
        /// Add property to this node.
        /// </summary>
        /// <param name="name">Property.</param>
        public void AddProperty(Property property)
        {
            mProperties.Add(property);
        }

        /// <summary>
        /// Add property to this node.
        /// </summary>
        /// <param name="name">Property name.</param>
        /// <param name="value">Property value.</param>
        public void AddProperty(string name, string value)
        {
            mProperties.Add(new Property(name, value));
        }

        /// <summary>
        /// Add property to this node.
        /// </summary>
        /// <param name="name">Property name.</param>
        /// <param name="value">Property value.</param>
        public void AddProperty(string name, object value)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            mProperties.Add(new Property(name, value));
        }

        /// <summary>
        /// Remove property from this node.
        /// </summary>
        /// <param name="name">Property name.</param>
        public void RemoveProperty(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            for (int i = mProperties.Count - 1; i >= 0; i--)
            {
                if (mProperties[i].Name.Equals(name))
                {
                    mProperties.RemoveAt(i);
                }
            }
        }

        public string ToString(bool output)
        {
            if (output)
            {
                using (Stream stream = new MemoryStream())
                {
                    GetData(new DataFormats.SimpleC2(), stream);

                    byte[] data = new byte[stream.Length];
                    stream.Read(data, 0, data.Length);

                    return System.Text.Encoding.UTF8.GetString(data, 0, data.Length);
                }
            }
            else
            {
                return ToString();
            }
        }

        /// <summary>
        /// Get property value from this node.
        /// </summary>
        /// <typeparam name="T">Value type.</typeparam>
        /// <param name="fromProperty">Property name.</param>
        /// <returns>Value from the property.</returns>
        public T GetPropertyValue<T>(string fromProperty)
        {
            string value = GetPropertyValue(fromProperty);

            if (value == null)
            {
                throw new Exception("Property '" + fromProperty + "' not found.");
            }

            // Try to parse.
            return Cache<T>.GenericParse(value);
        }

        /// <summary>
        /// Get property value from this node.
        /// </summary>
        /// <typeparam name="T">Value type.</typeparam>
        /// <param name="fromProperty">Property name.</param>
        /// <param name="defaultValue">Default value if property value do not exist.</param>
        /// <returns>Value from the property.</returns>
        public T GetPropertyValue<T>(string fromProperty, T defaultValue)
        {
            string v = GetPropertyValue(fromProperty);

            if(v == null)
            {
                return defaultValue;
            }

            // Try to parse.
            return Cache<T>.GenericParse(v);
        }

        /// <summary>
        /// Get raw property value.
        /// </summary>
        /// <param name="fromProperty">Property name.</param>
        /// <returns>Value from the property.</returns>
        public string GetPropertyValue(string fromProperty)
        {
            for (int i = Properties.Count - 1; i > -1; --i)
            {
                if (Properties[i].Name.Equals(fromProperty))
                {
                    return Properties[i].Value;
                }
            }
            return null;
        }

        /// <summary>
        /// Get node from a search parameter (Example "RootNode/Subnode/SubnodeNext")
        /// </summary>
        /// <param name="searchTree">Example "RootNode/Subnode/SubnodeNext"</param>
        /// <returns>Found node, otherwise null if no node found.</returns>
        public CustomNode Get(string searchTree)
        {
            string[] search = searchTree.Split('/');
            return Get(search);
        }

        /// <summary>
        /// Get node from a search parameter (Example "RootNode/Subnode/SubnodeNext")
        /// </summary>
        /// <param name="searchTree">Example "new string[] { "RootNode", "Subnode", "SubnodeNext" } </param>
        /// <returns>Found node, otherwise null if no node found.</returns>
        public CustomNode Get(string[] searchTree)
        {
            CustomNode stepFindedNode = this;
            for (int i = 0; i < searchTree.Length; ++i)
            {
                for (int j = 0; j < stepFindedNode.Nodes.Count; ++j)
                {
                    if (stepFindedNode.Nodes[j].Name.Equals(searchTree[i]))
                    {
                        stepFindedNode = stepFindedNode.Nodes[j];
                        break;
                    }
                }
            }

            if (stepFindedNode == this)
            {
                return null;
            }
            else
            {
                return stepFindedNode;
            }
        }

        /// <summary>
        /// Get data from a stream.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="stream"></param>
        public void GetData(IDataFormat format, Stream stream)
        {
            stream.Position = 0;

            format.InitFileStream(stream);
            format.Save(this);

            stream.Position = 0;
        }

        /// <summary>
        /// Set data to a stream.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="stream"></param>
        public void SetData(IDataFormat format, Stream stream)
        {
            stream.Position = 0;

            format.InitFileStream(stream);
            format.Load(this);

            stream.Position = 0;
        }

        public override string ToString()
        {
            return Name + " - nodes: " + Nodes.Count + ", props: " + Properties.Count;
        }

        public void Reset(CustomNode newNode)
        {
            Name = newNode.Name;

            mNodes.Clear();
            mProperties.Clear();

            foreach (var node in newNode.Nodes)
            {
                AddNode(node);
            }
            foreach (var property in newNode.Properties)
            {
                AddProperty(property);
            }
        }
    }
}
